/*
 * Licensed to the 南京汇海交通科技有限公司
 *
 * Copyright (c) 2025.
 * All rights reserved.
 *
 * 本软件属南京汇海交通科技有限公司所有，在未获得南京汇海交通科技有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.chframework.cloud.web.advice;

import com.chframework.cloud.web.advice.basic.AnnotationPointcutAdvisor;
import com.chframework.cloud.web.advice.basic.ServiceMethodInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

import java.lang.annotation.Annotation;
import java.util.Set;

/**
 * 自定义日志拦截切面
 * 拦截通过注解标注的接口和接口方法在所有子类的实现方法
 * 在这里我们不再自定义注解，直接拦截Spring-Web自带的注解 RequestMapping、GetMapping、PostMapping、DeleteMapping、PutMapping，
 * 实现与HttpAspect相同的效果，不同的是它只拦截个别类，而我们拦截的是接口下所有的子类，作用范围更广也更灵活，比较依赖于硬编码。
 * <p>
 * 个人习惯是：类注解限定拦截范围 + 方法注解限定最终的拦截条件。
 */
@Slf4j
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(
        prefix = "application.service",
        name = {"enable-aspect"},
        havingValue = "true",
        matchIfMissing = true
)
@ConditionalOnClass(FeignClient.class)
public class ServiceAspect {

    /**
     * 特征：接口上标注 FeignClient + 接口方法上标注 PostMapping。
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 标识Bean的分类
    public Advisor advisorForPostMappingPointcut() {
        return advisorIntersectionBuilder(FeignClient.class, PostMapping.class, 0);
    }

    /**
     * 特征：接口上标注 FeignClient + 接口方法上标注 GetMapping。
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 标识Bean的分类
    public Advisor advisorForGetMappingPointcut() {
        return advisorIntersectionBuilder(FeignClient.class, GetMapping.class, 0);
    }

    /**
     * 特征：接口上标注 FeignClient + 接口方法上标注 DeleteMapping。
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 标识Bean的分类
    public Advisor advisorForDeleteMappingPointcut() {
        return advisorIntersectionBuilder(FeignClient.class, DeleteMapping.class, 0);
    }

    /**
     * 特征：接口上标注 FeignClient + 接口方法上标注 PutMapping。
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 标识Bean的分类
    public Advisor advisorForPutMappingPointcut() {
        return advisorIntersectionBuilder(FeignClient.class, PutMapping.class, 0);
    }

    /*
     * 特征：接口上标注 FeignClient + 接口方法上标注 RequestMapping。
     * 注入 RequestMapping切面 会导致重复拦截，是它与上面几位兄弟有血缘关系的缘故吧，哎，重复调试若干次，都数不清了
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 标识Bean的分类
    public Advisor advisorForRequestMappingPointcut() {
        return advisorIntersectionBuilder(FeignClient.class, PutMapping.class, AspectOrderEnum.REQUEST_ADVICE.getOrdered());
    }*/

    /**
     * 拦截通过注解标注的接口方法在所有子类的实现方法。只拦截方法哦~~~
     */
    private Advisor advisorBuilder(@NonNull Class<? extends Annotation> methodAnnotationType, int order) {
        Assert.notNull(methodAnnotationType, "'methodAnnotationType' must not be null");

        // 限定方法级别的切点
        Pointcut methodPointcut = new AnnotationMatchingPointcut(null, methodAnnotationType, true);

        // 微服务日志拦截切面
        MethodInterceptor interceptor = new ServiceMethodInterceptor();

        // 组合切面
        ComposablePointcut pointcut = new ComposablePointcut(methodPointcut.getMethodMatcher()); // be careful

        // 切面增强类
        AnnotationPointcutAdvisor advisor = new AnnotationPointcutAdvisor(pointcut, interceptor);
        // 切面优先级顺序
        advisor.setOrder(order);

        return advisor;
    }

    /**
     * 拦截通过注解标注的接口和接口方法在所有子类的实现方法，求交集。
     */
    private Advisor advisorIntersectionBuilder(@NonNull Class<? extends Annotation> classAnnotationType, @NonNull Class<? extends Annotation> methodAnnotationType, int order) {
        Assert.notNull(classAnnotationType, "'classAnnotationType' must not be null");
        Assert.notNull(methodAnnotationType, "'methodAnnotationType' must not be null");

        // 限定类级别的切点
        Pointcut clazzPointcut = new AnnotationMatchingPointcut(classAnnotationType, true);
        // 限定方法级别的切点
        Pointcut methodPointcut = new AnnotationMatchingPointcut(null, methodAnnotationType, true);
        // 组合切面(交集)
        ComposablePointcut pointcut = new ComposablePointcut(clazzPointcut).intersection(methodPointcut);
        // 组合切面(并集)
        // ComposablePointcut pointcut = new ComposablePointcut(clazzPointcut).union(methodPointcut);
        // 微服务日志拦截切面
        MethodInterceptor interceptor = new ServiceMethodInterceptor();
        // 切面增强类
        AnnotationPointcutAdvisor advisor = new AnnotationPointcutAdvisor(pointcut, interceptor);
        // 切面优先级顺序
        advisor.setOrder(order);

        return advisor;
    }

    /**
     * 构建切面，求并集，被拦截的范围很大
     */
    private Pointcut buildPointcutByUnion(@NonNull Set<Class<? extends Annotation>> annotationTypeSet) {
        Assert.notNull(annotationTypeSet, "'annotationTypeSet' must not be null");

        ComposablePointcut result = null;
        for (Class<? extends Annotation> annotationType : annotationTypeSet) {
            // 限定类级别的切点
            // 请注意，如果将注解 annotationType 标注在接口上，则在继承了该接口的任何子类中的方法(包括父类或其它接口的方法)都将被拦截。
            Pointcut cpc = new AnnotationMatchingPointcut(annotationType, true);
            // 限定方法级别的切点
            // 请注意，如果将注解 annotationType 标注在接口方法上，则在继承了该接口的任何子类中，这个方法都将被拦截；未标注该注解的接口方法，在任何子类中都不会被拦截。
            Pointcut mpc = AnnotationMatchingPointcut.forMethodAnnotation(annotationType);
            if (result == null) {
                // 请注意，此处使用了方法 ComposablePointcut.union(Pointcut)，表示将以上两个切点的拦截范围进行合并(求并集)，
                // 除此，还可以使用ComposablePointcut下的其它方法，对不同切点的拦截范围进行更复杂的组合，例如求交集，求补集。
                result = new ComposablePointcut(cpc)
                        .union(mpc);

                // result = new ComposablePointcut(cpc)
                //         .intersection(mpc);
            } else {
                // 请注意，此处使用了方法 ComposablePointcut.union(Pointcut)，表示将以上两个切点的拦截范围进行合并(求并集)，
                // 除此，还可以使用ComposablePointcut下的其它方法，对不同切点的拦截范围进行更复杂的组合，例如求交集，求补集。
                result.union(cpc)
                        .union(mpc);

                // result.intersection(cpc)
                //         .intersection(mpc);
            }
        }
        // 总结，灵活使用该方法中的API，能够满足实际开发过程中各种复杂的需求场景(纯技术角度，与业务无关)。
        return result;
    }

}
